Xceed .NET Libraries Documentation
Welcome to Xceed Data Manipulation Compoents for .NET and .NET Standard / Basic Concepts / SFTP Capabilities / Keyboard Interactive Authentication

In This Topic
    Keyboard Interactive Authentication
    In This Topic

    SSHClient supports keyboard interactive authentication as defined by RFC 4256. Depending on the requirements of the SSH server, it can be used instead of password authentication.

    Keyboard interactive is a general purpose authentication method, suitable for interactive authentications where the authentication data is entered via a keyboard or equivalent alphanumeric input device. The major goal of this method is to allow the SSH client to support a whole class of authentication mechanisms without knowing the specifics of the actual authentication mechanisms.

    In practical terms, with keyboard interactive authentication, the SSH server sends text prompts to the client. These prompts must be given a text answer to. The client sends the responses back to the server. If accepted, more prompts can be sent to the client for response or the authentication can be declared successful or failed.

    The text prompts are arbitrary strings. There are no standard or predefined texts. The server decides the content of the prompts and what the required response is. As such, the SSHClient class cannot parse or process the prompts it receives as part of keyboard interactive authentication. Your application must process the prompts and supply responses programmatically or display the prompts to the end-user for them to type the responses.

    To authenticate with keyboard interactive, call Authenticate with a userName and a KeyBoardInteractiveAuthenticationHandler delegate.

    ssh.Authenticate( "user name", myHandler );
    ssh.Authenticate("user name", myHandler)

    The delegate KeyBoardInteractiveAuthenticationHandler is a callback method that will be invoked as part of keyboard interactive authentication. It is defined as follows:

    public delegate void KeyBoardInteractiveAuthenticationHandler(
      string userName,
      string name,
      string instruction,
      string languageTag,
      KeyboardInteractiveRequest[] requests );
    Public Delegate Sub KeyBoardInteractiveAuthenticationHandler(ByVal userName As String, ByVal name As String, ByVal instruction As String, ByVal languageTag As String, ByVal requests() As KeyboardInteractiveRequest)

    Where:

    userName is the user name that was specified in the call to the Authenticate method.

    name is a server-supplied string that may indicate a logical name for the series of requests. It can be an empty string, but it will not be null.

    instruction is a server-supplied string that may indicate instructions on how to respond to the series of requests. It can be an empty string, but it will not be null.

    languageTag is a string that specifies the language of the messages. In most cases, this parameter will be an empty string, and the language will be English.

    requests is an array of KeyboardInteractiveRequest objects that specify the information prompts and their responses. The array will not be null. Each element in the array represents a prompt that must be answered. The response for each element is prefilled with an empty string. It is possible that the array will be empty, but it will not be null.

    KeyboardInteractiveRequest is a simple class that holds the following properties:

    Prompt is the server-supplied string that specifies what information is required. For example, it could be something like "Password:". There are no pre-defined prompt strings. The server can supply any text it wants here.

    Echo is a boolean value that specifies whether the response to the prompt should be echoed to the screen. In general, this value will be false when the prompt refers to sensitive information like passwords and true otherwise.

    Response is the string that will contain the response to the prompt. Your handler will set the this property's value. The string will then be sent back to the server for authentication. The response can be set to an empty string. If the property is set to null, an empty string will be sent back to the server.

    Each request in the requests array represents a prompt. If the prompts are presented to an end-user, each prompt should be displayed to the user one by one and in order. The requests array will typically contain one prompt but it might contain more. The KeyBoardInteractiveAuthenticationHandler might be called again with more prompts. There is no predefined limit on the number of prompts that may be asked.

    The KeyBoardInteractiveAuthenticationHandler will be invoked by the component on the same thread that Authenticate() was called on. Authenticate() will therefore block while it waits for the KeyBoardInteractiveAuthenticationHandler to return. The component does not impose any timeout on how long control can stay in the handler. However, be aware that some SSH servers enforce a limit on how long authentication takes. For example, the default limit on the OpenSSH server is 120 seconds.

    How to implement KeyBoardInteractiveAuthenticationHandler

    If you are certain of the contents and formatting of the prompts you will receive from the server, you may implement a KeyBoardInteractiveAuthenticationHandler method that processes and answers the prompts automatically.

    Consider this example for OpenSSH servers that use the ChallengeResponseAuthentication option:

    public static void KeyBoardInteractiveAuthenticationHandlerLinuxPAM( string userName, string name, string instruction, string languageTag, KeyboardInteractiveRequest[] requests )
    {
      // If we have a request
      if( requests.Length > 0 )
      {
        // If the first request is the string 'Password: '
        if( StringComparer.OrdinalIgnoreCase.Compare( requests[ 0 ].Prompt, "Password: " ) == 0 )
        {
          // Supply our password as the response
          requests[ 0 ].Response = "<your password>";
        }
      }
    }
    Public Shared Sub KeyBoardInteractiveAuthenticationHandlerLinuxPAM(ByVal userName As String, ByVal name As String, ByVal instruction As String, ByVal languageTag As String, ByVal requests() As KeyboardInteractiveRequest)
      ' If we have a request
      If requests.Length > 0 Then
        ' If the first request is the string 'Password: '
        If StringComparer.OrdinalIgnoreCase.Compare(requests(0).Prompt, "Password: ") = 0 Then
          ' Supply our password as the response
          requests(0).Response = "<your password>"
        End If
      End If
    End Sub

    Another way to approach this authentication is to display the prompts to the console and accept input for the answers.

    public static void KeyBoardInteractiveAuthenticationHandlerConsole( string userName, string name, string instruction, string languageTag, KeyboardInteractiveRequest[] requests )
    {
      // If the name is non-empty
      if( !String.IsNullOrEmpty( name ) )
      {
        // Display it
        Console.WriteLine( name );
      }
    
      // If the instruction is non-empty
      if( !String.IsNullOrEmpty( instruction ) )
      {
        // Display it
        Console.WriteLine( instruction );
      }
    
      // If we have a request
      if( requests.Length > 0 )
      {
        // Go through each request in order
        foreach( KeyboardInteractiveRequest request in requests )
        {
          // Display the prompt
          Console.Write( request.Prompt );
    
          // If we can display the response as it is being typed
          if( request.Echo )
          {
            // Read the next line of text from the console
            request.Response = Console.ReadLine();
          }
          // We can't display the response
          else
          {
            StringBuilder response = new StringBuilder();
    
            // Read a key without displaying it
            ConsoleKeyInfo keyInfo = Console.ReadKey( true );
    
            // Until <Enter> is pressed
            while( keyInfo.Key != ConsoleKey.Enter )
            {
              // Add it to the response
              response.Append( keyInfo.KeyChar );
    
              // Read a key without displaying it
              keyInfo = Console.ReadKey( true );
            }
    
            // Store the response string in the request
            request.Response = response.ToString();
          }
        }
      }
    }
    Public Shared Sub KeyBoardInteractiveAuthenticationHandlerConsole(ByVal userName As String, ByVal name As String, ByVal instruction As String, ByVal languageTag As String, ByVal requests() As KeyboardInteractiveRequest)
      ' If the name is non-empty
      If (Not String.IsNullOrEmpty(name)) Then
        ' Display it
        Console.WriteLine(name)
      End If
    
      ' If the instruction is non-empty
      If (Not String.IsNullOrEmpty(instruction)) Then
        ' Display it
        Console.WriteLine(instruction)
      End If
    
      ' If we have a request
      If requests.Length > 0 Then
        ' Go through each request in order
        For Each request As KeyboardInteractiveRequest In requests
          ' Display the prompt
          Console.Write(request.Prompt)
    
          ' If we can display the response as it is being typed
          If request.Echo Then
            ' Read the next line of text from the console
            request.Response = Console.ReadLine()
            ' We can't display the response
          Else
            Dim response As New StringBuilder()
    
            ' Read a key without displaying it
            Dim keyInfo As ConsoleKeyInfo = Console.ReadKey(True)
    
            ' Until <Enter> is pressed
            Do While keyInfo.Key <> ConsoleKey.Enter
              ' Add it to the response
              response.Append(keyInfo.KeyChar)
    
              ' Read a key without displaying it
              keyInfo = Console.ReadKey(True)
            Loop
    
            ' Store the response string in the request
            request.Response = response.ToString()
          End If
        Next request
      End If
    End Sub

    Here's an example that puts it all together, with the exceptions that Authenticate can throw when called for the keyboard interactive method.

    static void Example()
    {
      string host = "<host>";
      string username = "<username>";
    
      using( SSHClient ssh = new SSHClient() )
      {
        ssh.Connect( host );
    
        KeyBoardInteractiveAuthenticationHandler consoleHandler = new KeyBoardInteractiveAuthenticationHandler( KeyBoardInteractiveAuthenticationHandlerConsole );
    
        bool retry = false;
    
        do
        {
          try
          {
            // Authenticate with the keyboard interactive method
            ssh.Authenticate( username, consoleHandler );
          }
          // Authentication was successful but more authentication is required by the server
          catch( SSHAuthenticationPartialSuccessException e )
          {
            // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
            e.AuthenticationsThatCanContinue.ToString();
    
            throw;
          }
          // The server rejected one of the keyboard response the user typed. Authentication has failed
          catch( SSHIncorrectResponseException e )
          {
            SSHAuthenticationFailedException authenticationFailedException = e.InnerException as SSHAuthenticationFailedException;
    
            if( authenticationFailedException != null )
            {
              // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
              authenticationFailedException.AuthenticationsThatCanContinue.ToString();
            }
    
            /* TODO: Decide if it is wise to retry the keyboard interactive method, how many times, etc */
            retry = true;
    
            if( !retry )
            {
              throw;
            }
          }
          catch( SSHUnsupportedAuthenticationMethodException e )
          {
            // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
            e.AuthenticationsThatCanContinue.ToString();
    
            throw;
          }
          catch( SSHAuthenticationFailedException e )
          {
            // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
            e.AuthenticationsThatCanContinue.ToString();
    
            throw;
          }
        }
        while( retry );
    
        /* TODO: Perform SSH/SFtp operations */
        using( SFtpSession sftp = new SFtpSession( ssh ) )
        {
    
        }
      }
    }
    Private Shared Sub Example()
      Dim host As String = "<host>"
      Dim username As String = "<username>"
    
      Using ssh As New SSHClient()
        ssh.Connect(host)
    
        Dim consoleHandler As New KeyBoardInteractiveAuthenticationHandler(AddressOf KeyBoardInteractiveAuthenticationHandlerConsole)
    
        Dim retry As Boolean = False
    
        Do
          Try
            ' Authenticate with the keyboard interactive method
            ssh.Authenticate(username, consoleHandler)
            ' Authentication was successful but more authentication is required by the server
          Catch e As SSHAuthenticationPartialSuccessException
            ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
            e.AuthenticationsThatCanContinue.ToString()
    
            Throw
            ' The server rejected one of the keyboard response the user typed. Authentication has failed
          Catch e As SSHIncorrectResponseException
            Dim authenticationFailedException As SSHAuthenticationFailedException = TryCast(e.InnerException, SSHAuthenticationFailedException)
    
            If authenticationFailedException IsNot Nothing Then
              ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
              authenticationFailedException.AuthenticationsThatCanContinue.ToString()
            End If
    
            ' TODO: Decide if it is wise to retry the keyboard interactive method, how many times, etc 
            retry = True
    
            If (Not retry) Then
              Throw
            End If
          Catch e As SSHUnsupportedAuthenticationMethodException
            ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
            e.AuthenticationsThatCanContinue.ToString()
    
            Throw
          Catch e As SSHAuthenticationFailedException
            ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
            e.AuthenticationsThatCanContinue.ToString()
    
            Throw
          End Try
        Loop While retry
    
        ' TODO: Perform SSH/SFtp operations 
        Using sftp As New SFtpSession(ssh)
    
        End Using
      End Using
    End Sub

    Some servers require multiple authentications be used to log in. Here is an example that chains different authentications one after the other.

    static void ChainingExample()
    {
      string host = "<host>";
      string username = "<username>";
      string password = "<password>";
    
      using( SSHClient ssh = new SSHClient() )
      {
        ssh.Connect( host );
    
        bool needsMoreAuthentication = false;
    
        try
        {
          // Authenticate with the username/password method
          ssh.Authenticate( username, password );
        }
        // Authentication was successful but more authentication is required by the server
        catch( SSHAuthenticationPartialSuccessException e )
        {
          /* We have successfully authenticated with username/password, but the server needs us
           * to use additional authentication methods */
          needsMoreAuthentication = true;
    
          // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString();
        }
        catch( SSHUnsupportedAuthenticationMethodException e )
        {
          /* The server doesn't support this authentication. We will use additional authentication
            * methods we support */
          needsMoreAuthentication = true;
    
          // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString();
        }
        catch( SSHAuthenticationFailedException e )
        {
          // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString();
    
          throw;
        }
    
        // If we need to perform more authentication
        if( needsMoreAuthentication )
        {
          /* We will use the other authentication method we have */
    
          KeyBoardInteractiveAuthenticationHandler consoleHandler = new KeyBoardInteractiveAuthenticationHandler( KeyBoardInteractiveAuthenticationHandlerConsole );
    
          bool retry = false;
    
          do
          {
            try
            {
              // Authenticate with the keyboard interactive method
              ssh.Authenticate( username, consoleHandler );
    
              // If we reach here, we have used all our authentication methods and they have been successful
              needsMoreAuthentication = false;
            }
            // Authentication was successful but more authentication is required by the server
            catch( SSHAuthenticationPartialSuccessException e )
            {
              /* At this point, we are out of authentication methods... */
    
              throw new Exception( "Can't authenticate. Used up all authentication methods we support.", e );
            }
            // The server rejected one of the keyboard response the user typed. Authentication has failed
            catch( SSHIncorrectResponseException e )
            {
              SSHAuthenticationFailedException authenticationFailedException = e.InnerException as SSHAuthenticationFailedException;
    
              if( authenticationFailedException != null )
              {
                // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
                authenticationFailedException.AuthenticationsThatCanContinue.ToString();
              }
    
              /* TODO: Decide if it is wise to retry the keyboard interactive method, how many times, etc */
              retry = true;
    
              if( !retry )
              {
                throw;
              }
            }
            catch( SSHUnsupportedAuthenticationMethodException e )
            {
              // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
              e.AuthenticationsThatCanContinue.ToString();
    
              /* At this point, we are out of authentication methods... */
    
              throw new Exception( "Can't authenticate. Used up all authentication methods we support.", e );
            }
            catch( SSHAuthenticationFailedException e )
            {
              // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
              e.AuthenticationsThatCanContinue.ToString();
    
              throw;
            }
          }
          while( retry );
        }
    
        /* TODO: Perform SSH/SFtp operations */
        using( SFtpSession sftp = new SFtpSession( ssh ) )
        {
    
        }
      }
    }
    Private Shared Sub ChainingExample()
      Dim host As String = "<host>"
      Dim username As String = "<username>"
      Dim password As String = "<password>"
    
      Using ssh As New SSHClient()
        ssh.Connect(host)
    
        Dim needsMoreAuthentication As Boolean = False
    
        Try
          ' Authenticate with the username/password method
          ssh.Authenticate(username, password)
          ' Authentication was successful but more authentication is required by the server
        Catch e As SSHAuthenticationPartialSuccessException
          '           We have successfully authenticated with username/password, but the server needs us
          '           * to use additional authentication methods 
          needsMoreAuthentication = True
    
          ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString()
        Catch e As SSHUnsupportedAuthenticationMethodException
          '           The server doesn't support this authentication. We will use additional authentication
          '            * methods we support 
          needsMoreAuthentication = True
    
          ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString()
        Catch e As SSHAuthenticationFailedException
          ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString()
    
          Throw
        End Try
    
        ' If we need to perform more authentication
        If needsMoreAuthentication Then
          ' We will use the other authentication method we have 
    
          Dim consoleHandler As New KeyBoardInteractiveAuthenticationHandler(AddressOf KeyBoardInteractiveAuthenticationHandlerConsole)
    
          Dim retry As Boolean = False
    
          Do
            Try
              ' Authenticate with the keyboard interactive method
              ssh.Authenticate(username, consoleHandler)
    
              ' If we reach here, we have used all our authentication methods and they have been successful
              needsMoreAuthentication = False
              ' Authentication was successful but more authentication is required by the server
            Catch e As SSHAuthenticationPartialSuccessException
              ' At this point, we are out of authentication methods... 
    
              Throw New Exception("Can't authenticate. Used up all authentication methods we support.", e)
              ' The server rejected one of the keyboard response the user typed. Authentication has failed
            Catch e As SSHIncorrectResponseException
              Dim authenticationFailedException As SSHAuthenticationFailedException = TryCast(e.InnerException, SSHAuthenticationFailedException)
    
              If authenticationFailedException IsNot Nothing Then
                ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
                authenticationFailedException.AuthenticationsThatCanContinue.ToString()
              End If
    
              ' TODO: Decide if it is wise to retry the keyboard interactive method, how many times, etc 
              retry = True
    
              If (Not retry) Then
                Throw
              End If
            Catch e As SSHUnsupportedAuthenticationMethodException
              ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
              e.AuthenticationsThatCanContinue.ToString()
    
              ' At this point, we are out of authentication methods... 
    
              Throw New Exception("Can't authenticate. Used up all authentication methods we support.", e)
            Catch e As SSHAuthenticationFailedException
              ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
              e.AuthenticationsThatCanContinue.ToString()
    
              Throw
            End Try
          Loop While retry
        End If
    
        ' TODO: Perform SSH/SFtp operations 
        Using sftp As New SFtpSession(ssh)
    
        End Using
      End Using
    End Sub
    See Also

    General information